#pragma once

#ifndef _MY_STRATEGY_H_
#define _MY_STRATEGY_H_

#include <stack>
#include <unordered_set>
#include <fstream>

// lol
#define private public

#include "Strategy.h"

#undef private

class Logger : public std::ofstream
{
    Logger();
public:
    static Logger& instance();
};

class LoggerWrapper
{
    std::ostream& mStream;
public:
    LoggerWrapper(std::ostream& stream): mStream(stream) {}
    ~LoggerWrapper() { mStream << std::endl; }

    template<typename T>
    LoggerWrapper& operator<<(const T& val)
    {
        mStream << val;
        return *this;
    }
};

const bool HasLogging = true;

#define LOG()  if(HasLogging) Logger::instance()
#define LOGL() if(HasLogging) LoggerWrapper(Logger::instance())

template<typename T, int MaxSize>
class StackVector
{
public:
    typedef StackVector<T, MaxSize> this_type;
    typedef T value_type;
    typedef T& reference;
    typedef const T& const_reference;
    typedef int size_type;
    typedef T* iterator;
    typedef const T* const_iterator;
private:
    struct Placeholder
    {
        char padding[sizeof(T)];
    };

    Placeholder mPlaceholders[MaxSize];
    T* mData;
    size_type mSize;
public:

    StackVector():
        mData((T*)&mPlaceholders[0]), // hack hack hack
        mSize(0)
    {
    }

    StackVector(const this_type& other):
        mData((T*)&mPlaceholders[0]), // hack hack hack
        mSize(other.size())
    {
        for(int i = 0; i < mSize; ++i)
        {
            new(mData + i) T(other[i]);
        }
    }

    template<class InputIt>
    StackVector(InputIt first, InputIt last):
        mData((T*)&mPlaceholders[0]), // hack hack hack
        mSize(0)
    {
        while(first != last)
        {
            push_back(*first);
            ++first;
        }
    }

    StackVector(const std::vector<T>& other):
        mData((T*)&mPlaceholders[0]), // hack hack hack
        mSize(other.size())
    {
        /*if(other.size() > MaxSize)
        {
            exit(321);
        }*/
        for(int i = 0; i < mSize; ++i)
        {
            new(mData + i) T(other[i]);
        }
    }


    ~StackVector()
    {
        clear();
    }

    this_type& operator=(const this_type& other)
    {
        clear();
        std::copy(other.begin(), other.end(), std::back_inserter(*this));
        return *this;
    }

    this_type& operator=(const std::vector<T>& other)
    {
        /*if(other.size() > MaxSize)
        {
            exit(321);
        }*/
        clear();
        std::copy(other.begin(), other.end(), std::back_inserter(*this));
        return *this;
    }

    size_type size() const { return mSize; }

    bool empty() const { return 0 == mSize; }

    void push_back(const T& val)
    {
        if(mSize >= MaxSize)
        {
            //exit(321);
            return;
        }
        new(mData + mSize) T(val);
        ++mSize;
    }

    void pop_back()
    {
        --mSize;
        mData[mSize].~T();
    }

    void clear()
    {
        for(int i = 0; i < mSize; ++i) mData[i].~T();
        mSize = 0;
    }

    T& operator[](size_t index) const
    {
        return mData[index];
    }

    void resize(size_type newSize)
    {
        if(newSize > mSize)
        {
            for(size_type i = mSize; i < newSize; ++i)
            {
                new(mData + i) T;
            }
        }
        else
        {
            for(size_type i = newSize; i < mSize; --i)
            {
                mData[i].~T();
            }
        }
        mSize = newSize;
    }

    T* begin() const { return mData; }
    T* end()   const { return mData + mSize; }

    T& front() const { return mData[0]; }
    T& back()  const { return mData[mSize - 1]; }
};

//namespace std
//{
//template<typename First, typename Second>
//struct hash<std::pair<First, Second> >
//{
//    std::size_t operator()(const std::pair<First, Second>& p) const
//    {
//        return std::hash<First>()(p.first) ^
//               std::hash<Second>()(p.second);
//    }
//};
//}

class MyStrategyImpl
{
    static const int MapUpdateInterval = 10;

    static const int TrooperTypesCount = model::SCOUT + 1;
    static const int TeamMaxTroopersCount = TrooperTypesCount;
    static const int StanceTypesCount = model::_TROOPER_STANCE_COUNT_;

    static const int UnknownDangerMult = 100;

    static const int MaxPathWeight = 999;
    static const int PathWeightInc = 75;

    static const int DangerDecay = 10;

    static const int AttackScoreMult          = 1000;
    static const int HealingScoreMult         = 900;
    static const int EnemyAttackScoreMult     = 800;
    static const int EnemyAttackScoreMultSelf = 2500;
    static const int BonusUsePenality         = 100;
    static const int RequestEnenmyDispBonus   = 2500;
    static const int OutOfEnemySightBonus     = 30000;

    static const int MaxTroopers = 4 * TeamMaxTroopersCount;
    static const int MaxBonuses = 10;

    static const int MaxAttackMoveTasks  = 3;
    static const int MaxAttackGMoveTasks = 3;
    static const int MaxRetreatMoveTasks = 5;
    static const int MaxAdvanceMoveTasks = 5;

    static const int MaxSimulationDepth = 10;
    static const int BestStrategiesCount = 64;
    static const int BruteForceThreshhold = 20000;

    struct Task;
    class PathHelper;
    //typedef std::vector<model::Trooper> TroopersVec;
    //typedef std::vector<model::Bonus>   BonusesVec;
    typedef std::vector<Task> TaskVecType;

    typedef StackVector<model::Trooper, MaxTroopers> TroopersVec;
    typedef StackVector<model::Bonus,   MaxBonuses>   BonusesVec;

    struct Task
    {
        model::ActionType action;
        int x;
        int y;
        model::Direction dir;
        int count;
        int trooperInd;
        int bonusInd;
        int weight;
        int apCost;
        Task():
            action(model::UNKNOWN_ACTION),
            x(-1),
            y(-1),
            dir(model::UNKNOWN_DIRECTION),
            count(1),
            trooperInd(-1),
            bonusInd(-1),
            weight(0),
            apCost(0) {}

        void setCoord(int x_, int y_)
        {
            x = x_;
            y = y_;
        }

        bool operator<(const Task& other) const
        {
            return weight < other.weight;
        }

        void apply(const MyStrategyImpl* myStrategy,
                   const model::Trooper& self,
                   const TroopersVec& troopers,
                   model::Move& m);
    };

    enum TrooperRole
    {
        RoleLeader = 0,
        RoleSupport,
        RoleRetreat,
        RoleWander,

        MaxTrooperRoles
    };

    struct Cell
    {
        int weight;
        int weightUpdateIteration;

        int lastUpdate;
        int enemyDanger;
        int teamWeight;

        float unknownDanger[StanceTypesCount];
        float maxUnknownDanger;
        Cell():
            weight(0),
            weightUpdateIteration(-1),
            lastUpdate(-1),
            enemyDanger(0),
            teamWeight(0)
        {
            resetUnknownDanger();
        }

        void resetUnknownDanger()
        {
            for(int i = 0; i < StanceTypesCount; ++i) unknownDanger[i] = 0;
            maxUnknownDanger = 0;
        }

        void setWeight(int w, int i)
        {
            weight = w;
            weightUpdateIteration = i;
        }
    };

    struct TrooperStats
    {
        float aggresion;
        float defence;
        float support;
        float teamWork;
        TrooperStats():
            aggresion(1.0f),
            defence(1.0f),
            support(1.0f),
            teamWork(1.0f) {}
    };

    TrooperStats mStats[TrooperTypesCount];

    bool mInit;
    int mStartCorner;
    int mWidth;
    int mHeight;
    std::vector<std::vector<model::CellType> > mWorldCells;
    bool mNeedUpdateMap;
    int mLastMapUpdate;
    int mMapUpdateCount;
    bool mNeedUpdate;
    int mUpdateCount;
    int mRejectCount = 0;
    bool mDispositionReq;
    int mCreateCount = 0;
    TroopersVec mPreviousTroopers;
    BonusesVec  mPreviousBonuses;
    TaskVecType mActions;
    int mPreviousTeammate = -1;

    typedef std::vector<int> MoveOrderType;
    MoveOrderType mMoveOrder;

    Cell* mCells;
    Cell& getCell(int x, int y) const { return mCells[x + mWidth * y]; }

    void firstInit(const model::Trooper& self, const model::World& world, const model::Game& game);
    void updateMap(const model::Trooper& self, const model::World& world, const model::Game& game);
    void fillMap(int startX, int startY, int initialVal, int valDec, int skipDec = 0);
    void updateDanger(const model::Trooper& self, const model::World& world, const model::Game& game);
    static int calcDamage(const model::Trooper& t, model::TrooperStance st);
    static double calcShootingRange(const model::Trooper& t, const model::Game& g);
    static int calcMoveCost(const model::Game& game, model::TrooperStance st);
    static int calcTrooperStrength(const model::Trooper& t);
    int calcRetreatWeight(int x, int y, model::TrooperStance stance) const;
    int calcAdvanceWeight(int x, int y, model::TrooperStance stance) const;
    static bool isNear(int x1, int y1, int x2, int y2);
    bool isCellPassable(int x, int y) const;

    int getNextTrooper(int t) const;

    static bool isInFight(const model::Trooper& self, const TroopersVec& troopers);
    static int getTeamSize(const model::Trooper& self, const TroopersVec& troopers);
    static size_t calcHash(const model::Trooper& self,
                                       const TroopersVec& troopers,
                                       const BonusesVec& bonuses);

    template<typename F>
    static void applyTask(
            const Task& ac,
            const model::Trooper& self,
            const BonusesVec& bonuses,
            const TroopersVec& troopers,
            const model::World& world,
            const model::Game& game,
            F functor);

    template<typename F>
    static void applyTasks(
            const TaskVecType& tasks,
            const model::Trooper& self,
            const BonusesVec& bonuses,
            const TroopersVec& troopers,
            const model::World& world,
            const model::Game& game,
            F functor,
            TaskVecType::size_type firstTask = 0);

    struct TrooperRestrictions
    {
        bool canMove = true;
        bool canRaiseStance = true;
        bool canLowerStance = true;
        bool canHealSelf = true;
        std::unordered_set<size_t> movesToReject;
        std::unordered_set<int>    ignoreSoldier;
    };

    void createMoveTasks(const model::Trooper& self,
                         const TroopersVec& troopers,
                         const BonusesVec& bonuses,
                         const model::World& world,
                         const model::Game& game,
                         const TrooperRestrictions& restrictions,
                         TaskVecType& tasks) const;
    Task createEndTurnTask(const model::Trooper& self,
                           const TroopersVec& troopers,
                           const BonusesVec& bonuses,
                           const model::World& world,
                           const model::Game& game,
                           const model::Trooper** myTroopers,
                           int endTurnPenality) const;
    template<int MaxResultCount>
    StackVector<TaskVecType,MaxResultCount> createTasks(
            const model::Trooper& self,
            const TroopersVec& troopers,
            const BonusesVec& bonuses,
            const model::World& world,
            const model::Game& game,
            int depth,
            const TrooperRestrictions& restrictions = TrooperRestrictions());

    template<int MaxCount = BestStrategiesCount>
    TaskVecType calc(const model::Trooper& self,
                     const TroopersVec& troopers,
                     const BonusesVec& bonuses,
                     const model::World& world,
                     const model::Game& game,
                     int depth);

public:
    MyStrategyImpl();
    ~MyStrategyImpl();
    void move(const model::Trooper& self, const model::World& world, const model::Game& game, model::Move& move);

    static MyStrategyImpl instance;
};

class MyStrategy : public Strategy
{
public:
    void move(const model::Trooper& self, const model::World& world, const model::Game& game, model::Move& move)
    {
        MyStrategyImpl::instance.move(self, world, game, move);
    }
};

#endif
